package com.blyat.xsoft.kernel.component.mybatis.interceptor;

import com.blyat.xsoft.kernel.restful.pagination.Page;
import com.blyat.xsoft.kernel.util.ReflectionUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @author syh
 * @date 2019/8/26
 **/
@Intercepts({@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)})
public class PaginationInterceptor extends AbstractInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(PaginationInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (invocation.getTarget() instanceof Executor) {
            MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
            Object param = invocation.getArgs()[1];
            Page page = this.checkHasPage(param);
            if (page == null || !dialect.supportsLimit()) {
                return invocation.proceed();
            }

            BoundSql boundSql = ms.getBoundSql(param);
            logger.debug("开始执行分页处理。原始SQL：[{}], 页数：{}，每页记录数：{}", new Object[]{boundSql.getSql(), page.getPage(), page.getPageSize()});
            int count = this.getCount(ms, param, boundSql);
            page.setTotal(count);
            String pageSql = dialect.getLimitString(boundSql.getSql(), (page.getPage()) * page.getPageSize(), page.getPageSize());
            logger.debug("分页SQL：[{}]", pageSql);
            BoundSql pageBS = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), boundSql.getParameterObject());

            if (ReflectionUtil.getFieldValue("metaParameters", boundSql) != null) {
                MetaObject mo = (MetaObject) ReflectionUtil.getFieldValue("metaParameters", boundSql);
                ReflectionUtil.setField("metaParameters", mo, pageBS);
            }

            if (ReflectionUtil.getFieldValue("additionalParameters", boundSql) != null) {
                HashMap parameters = (HashMap) ReflectionUtil.getFieldValue("additionalParameters", boundSql);
                ReflectionUtil.setField("additionalParameters", parameters, pageBS);
            }

            invocation.getArgs()[0] = this.copyFromMappedStatement(ms, new BoundSqlSource(pageBS));
            invocation.getArgs()[2] = RowBounds.DEFAULT;
        }

        return invocation.proceed();
    }

    private int getCount(MappedStatement ms, Object param, BoundSql boundSql) throws SQLException {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        int var10;
        try {
            conn = ms.getConfiguration().getEnvironment().getDataSource().getConnection();
            String countSql = String.format("select count(1) from ( %s ) tmp", boundSql.getSql());
            ps = conn.prepareStatement(countSql);
            DefaultParameterHandler parameterHandler = new DefaultParameterHandler(ms, param, boundSql);
            parameterHandler.setParameters(ps);
            rs = ps.executeQuery();
            int count = 0;
            if (rs.next()) {
                count = rs.getInt(1);
            }

            var10 = count;
        } finally {
            if (rs != null) {
                rs.close();
            }

            if (ps != null) {
                ps.close();
            }

            if (conn != null) {
                conn.close();
            }

        }

        return var10;
    }

    private Page checkHasPage(Object param) {
        if (param != null) {
            if (param instanceof Map) {
                Map paramMap = (Map) param;
                if (paramMap.containsKey("page")) {
                    return (Page) paramMap.get("page");
                }
            } else {
                Object o = ReflectionUtil.getFieldValue("page", param);
                if (o != null) {
                    return (Page) o;
                }
            }
        }

        return null;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
    }

    public static class BoundSqlSource implements SqlSource {
        BoundSql boundSql;

        BoundSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        public BoundSql getBoundSql(Object parameterObject) {
            return this.boundSql;
        }
    }
}
